# ElementCollectionRelation.java

    package org.codefilarete.stalactite.engine.configurer.elementcollection;

    import javax.annotation.Nullable;
    import java.util.Collection;
    import java.util.Map;
    import java.util.function.Supplier;

    import org.codefilarete.reflection.Accessor;
    import org.codefilarete.reflection.AccessorByMethod;
    import org.codefilarete.reflection.AccessorByMethodReference;
    import org.codefilarete.reflection.AccessorChain;
    import org.codefilarete.reflection.AccessorChain.ValueInitializerOnNullValue;
    import org.codefilarete.reflection.Accessors;
    import org.codefilarete.reflection.MutatorByMethodReference;
    import org.codefilarete.reflection.PropertyAccessor;
    import org.codefilarete.reflection.ReversibleAccessor;
    import org.codefilarete.reflection.ValueAccessPoint;
    import org.codefilarete.reflection.ValueAccessPointMap;
    import org.codefilarete.stalactite.dsl.embeddable.EmbeddableMappingConfigurationProvider;
    import org.codefilarete.stalactite.engine.configurer.LambdaMethodUnsheller;
    import org.codefilarete.stalactite.sql.ddl.Size;
    import org.codefilarete.stalactite.sql.ddl.structure.Column;
    import org.codefilarete.stalactite.sql.ddl.structure.Table;
    import org.codefilarete.tool.Reflections;
    import org.danekja.java.util.function.serializable.SerializableBiConsumer;
    import org.danekja.java.util.function.serializable.SerializableFunction;

    import static org.codefilarete.tool.Reflections.propertyName;

    /**
     * Support for element-collection configuration
     *
     * @author Guillaume Mary
     */
    public class ElementCollectionRelation<SRC, TRGT, S extends Collection<TRGT>> {

    	/** The method that gives the entities from the "root" entity */
    	private final ReversibleAccessor<SRC, S> collectionAccessor;
    	private final Class<TRGT> componentType;
    	/** Indicator to store element indices in the database, retrieve them, and sort the collection while loading the entities */
    	private boolean ordered = false;
    	/** Optional provider of collection instance to be used if collection value is null */
    	private Supplier<S> collectionFactory;

    	private Table targetTable;
    	private String targetTableName;
    	private Column<Table, ?> reverseColumn;
    	private String reverseColumnName;

    	/** Element column name override, used in simple case : {@link EmbeddableMappingConfigurationProvider} null, aka not when element is a complex type */
    	private String elementColumnName;
    	/** Index column name override */
    	private String indexingColumnName;

    	/** Complex type mapping, optional */
    	@Nullable
    	private final EmbeddableMappingConfigurationProvider<TRGT> embeddableConfigurationProvider;

    	/** Complex type mapping override, to be used when {@link EmbeddableMappingConfigurationProvider} is not null */
    	private final ValueAccessPointMap<SRC, String> overriddenColumnNames = new ValueAccessPointMap<>();

    	/** Complex type mapping override, to be used when {@link EmbeddableMappingConfigurationProvider} is not null */
    	private final ValueAccessPointMap<SRC, Size> overriddenColumnSizes = new ValueAccessPointMap<>();

    	private Size elementColumnSize;

    	/**
    	 * @param setter collection accessor
    	 * @param componentType element type in collection
    	 * @param embeddableConfigurationProvider complex type mapping, null when element is a simple type (String, Integer, ...)
    	 */
    	public ElementCollectionRelation(SerializableBiConsumer<SRC, S> setter,
    									 Class<TRGT> componentType,
    									 @Nullable EmbeddableMappingConfigurationProvider<TRGT> embeddableConfigurationProvider) {
    		MutatorByMethodReference<SRC, S> setterReference = Accessors.mutatorByMethodReference(setter);
    		this.collectionAccessor = new PropertyAccessor<>(
    				Accessors.accessor(setterReference.getDeclaringClass(), propertyName(setterReference.getMethodName())),
    				setterReference
    		);
    		this.componentType = componentType;
    		this.embeddableConfigurationProvider = embeddableConfigurationProvider;
    	}

    	/**
    	 * @param getter collection accessor
    	 * @param componentType element type in collection
    	 * @param lambdaMethodUnsheller engine to capture getter method reference
    	 * @param embeddableConfigurationProvider complex type mapping, null when element is a simple type (String, Integer, ...)
    	 */
    	public ElementCollectionRelation(SerializableFunction<SRC, S> getter,
    									 Class<TRGT> componentType,
    									 LambdaMethodUnsheller lambdaMethodUnsheller,
    									 @Nullable EmbeddableMappingConfigurationProvider<TRGT> embeddableConfigurationProvider) {
    		AccessorByMethodReference<SRC, S> getterReference = Accessors.accessorByMethodReference(getter);
    		this.collectionAccessor = new PropertyAccessor<>(
    				// we keep close to user demand : we keep its method reference ...
    				getterReference,
    				// ... but we can't do it for mutator, so we use the most equivalent manner : a mutator based on setter method (fallback to property if not present)
    				new AccessorByMethod<SRC, S>(lambdaMethodUnsheller.captureLambdaMethod(getter)).toMutator());
    		this.componentType = componentType;
    		this.embeddableConfigurationProvider = embeddableConfigurationProvider;
    	}

    	public ElementCollectionRelation(ReversibleAccessor<SRC, S> collectionAccessor ,
    									 Class<TRGT> componentType,
    									 @Nullable EmbeddableMappingConfigurationProvider<TRGT> embeddableConfigurationProvider) {
    		this.collectionAccessor = collectionAccessor;
    		this.componentType = componentType;
    		this.embeddableConfigurationProvider = embeddableConfigurationProvider;
    	}

    	public ReversibleAccessor<SRC, S> getCollectionAccessor() {
    		return collectionAccessor;
    	}

    	public boolean isOrdered() {
    		return this.ordered;
    	}

    	public void setOrdered(boolean ordered) {
    		this.ordered = ordered;
    	}

    	public void ordered() {
    		this.ordered = true;
    	}

    	public void setIndexingColumnName(String columnName) {
    		ordered();
    		this.indexingColumnName = columnName;
    	}

    	public String getIndexingColumnName() {
    		return indexingColumnName;
    	}

    	public Supplier<S> getCollectionFactory() {
    		return collectionFactory;
    	}

    	public ElementCollectionRelation<SRC, TRGT, S> setCollectionFactory(Supplier<? extends S> collectionFactory) {
    		this.collectionFactory = (Supplier<S>) collectionFactory;
    		return this;
    	}

    	public ValueAccessPointMap<SRC, String> getOverriddenColumnNames() {
    		return this.overriddenColumnNames;
    	}

    	public void overrideName(SerializableFunction methodRef, String columnName) {
    		this.overriddenColumnNames.put(new AccessorByMethodReference(methodRef), columnName);
    	}

    	public void overrideName(SerializableBiConsumer methodRef, String columnName) {
    		this.overriddenColumnNames.put(new MutatorByMethodReference(methodRef), columnName);
    	}

    	public void overrideSize(SerializableFunction methodRef, Size columnSize) {
    		this.overriddenColumnSizes.put(new AccessorByMethodReference(methodRef), columnSize);
    	}

    	public void overrideSize(SerializableBiConsumer methodRef, Size columnSize) {
    		this.overriddenColumnSizes.put(new MutatorByMethodReference(methodRef), columnSize);
    	}

    	public ValueAccessPointMap<SRC, Size> getOverriddenColumnSizes() {
    		return this.overriddenColumnSizes;
    	}

    	public Table getTargetTable() {
    		return targetTable;
    	}

    	public void setTargetTable(Table targetTable) {
    		this.targetTable = targetTable;
    	}

    	public String getTargetTableName() {
    		return targetTableName;
    	}

    	public ElementCollectionRelation<SRC, TRGT, S> setTargetTableName(String targetTableName) {
    		this.targetTableName = targetTableName;
    		return this;
    	}

    	public <I> Column<Table, I> getReverseColumn() {
    		return (Column<Table, I>) reverseColumn;
    	}

    	public void setReverseColumn(Column<Table, ?> reverseColumn) {
    		this.reverseColumn = reverseColumn;
    	}

    	public String getReverseColumnName() {
    		return reverseColumnName;
    	}

    	public ElementCollectionRelation<SRC, TRGT, S> setReverseColumnName(String reverseColumnName) {
    		this.reverseColumnName = reverseColumnName;
    		return this;
    	}

    	public Class<TRGT> getComponentType() {
    		return componentType;
    	}

    	public EmbeddableMappingConfigurationProvider<TRGT> getEmbeddableConfigurationProvider() {
    		return embeddableConfigurationProvider;
    	}

    	public void setElementColumnName(String columnName) {
    		this.elementColumnName = columnName;
    	}

    	public String getElementColumnName() {
    		return elementColumnName;
    	}

    	public void setElementColumnSize(Size elementColumnSize) {
    		this.elementColumnSize = elementColumnSize;
    	}

    	public Size getElementColumnSize() {
    		return elementColumnSize;
    	}

    	/**
    	 * Clones this object to make one with the given accessor as prefix of current one.
    	 * Made to "slide" current instance with an accessor prefix. Used for embeddable objects with relation to make the relation being accessible
    	 * from the "root" entity.
    	 *
    	 * @param accessor the prefix of the clone to be created
    	 * @param embeddedType the concrete type of the embeddable bean, because accessor may provide an abstraction
    	 * @return a clones of this instance prefixed with the given accessor
    	 * @param <C> the root entity type that owns the embeddable which has this relation
    	 */
    	public <C> ElementCollectionRelation<C, TRGT, S> embedInto(Accessor<C, SRC> accessor, Class<SRC> embeddedType) {
    		AccessorChain<C, S> shiftedTargetProvider = new AccessorChain<>(accessor, collectionAccessor);
    		shiftedTargetProvider.setNullValueHandler(new ValueInitializerOnNullValue() {
    			@Override
    			protected <T> T newInstance(Accessor<?, T> segmentAccessor, Class<T> valueType) {
    				if (segmentAccessor == accessor) {
    					return (T) Reflections.newInstance(embeddedType);
    				} else if (segmentAccessor == collectionAccessor){
    					if (collectionFactory != null) {
    						return (T) collectionFactory.get();
    					} else {
    						return super.newInstance(segmentAccessor, valueType);
    					}
    				} else {
    					return super.newInstance(segmentAccessor, valueType);
    				}
    			}
    		});
    		ElementCollectionRelation<C, TRGT, S> result = new ElementCollectionRelation<C, TRGT, S>(shiftedTargetProvider, componentType, embeddableConfigurationProvider);

    		result.setElementColumnName(this.getElementColumnName());
    		result.setElementColumnSize(this.getElementColumnSize());
    		result.setTargetTable(this.getTargetTable());
    		result.setTargetTableName(this.getTargetTableName());
    		result.setReverseColumn(this.getReverseColumn());
    		result.setReverseColumnName(this.getReverseColumnName());
    		result.getOverriddenColumnNames().putAll((Map<? extends ValueAccessPoint<C>, ? extends String>) this.getOverriddenColumnNames());
    		result.getOverriddenColumnSizes().putAll((Map<? extends ValueAccessPoint<C>, ? extends Size>) this.getOverriddenColumnSizes());
    		result.setOrdered(this.isOrdered());
    		result.setIndexingColumnName(this.getIndexingColumnName());
    		result.setCollectionFactory(this.getCollectionFactory());
    		return result;
    	}
    }